"use strict";

class Event {
	constructor(object){
		this.list = {};
		this.object = object;
	}
	add(name, func, opts){
		let key = name.split(':')[0];
		let list = this.list[key] || (this.list[key] = []);
			list.push(Object.assign(opts || {}, {name, func}));
		return list.length;
	}
	once(name, func){
		return this.add(name, func, {once:true});
	}
	remove(name, func){
		let key = name.split(':')[0];
		let list = this.list[key];
		let items = list.filter(event => event.name == name && (func ? event.func == func : true));
			items.forEach(event => {
				list.splice(list.indexOf(event), 1);
			});
		return items.length;
	}
	clear(name){
		if( arguments.length > 1 ){
			return [...arguments].reduce((ret, name) => ret += this.clear(name), 0);
		}
		let list = this.list[name];
		if( list ){
			return list.splice(0, list.length).length;
		}
		return 0;
	}
	dispatch(name){
		let key = name.split(':')[0];
		let evn = name.indexOf(':') == -1 ? null : name;
		let list = this.list[key];
		if( list ){
			let items = list.filter(event => !evn || event.name == evn);
				items.forEach(event => {
					event.func(this.object);
					if( event.once ){
						list.splice(list.indexOf(event), 1);
					}
				});
			return items.length;
		}
		return 0;
	}
	fire(name){
		return this.dispatch(name);
	}
}

const parser = {
	general(str, re){
		if( str.charAt(0) != '[' ){
			return null;
		}
		let matcher = str.match(re);
		if( matcher ){
			let kin = 0;
			let ret = {prefix : matcher[0], matcher : matcher}
			let i = ret.prefix.length;
			while( i<str.length ){
				let char = str.charAt(i);
				if( char == '\\' ){
					char = str.charAt(++i);
				}
				else if( char == ']' ){
					kin--;
					if( kin < 0 ){
						ret.suffix = char;
						ret.content = str.substring(ret.prefix.length, i);
						ret.str = str.substring(0, i + ret.suffix.length);
						return ret;
					}
				}
				else if( char == '[' ){
					kin++;
				}
				i++;
			}
		}
	},
	simple(str, re, schar){
		if( str.charAt(0) != schar ){
			return null;
		}
		let matcher = str.match(re);
		if( matcher ){
			let kin = 0;
			let ret = {prefix : matcher[0], matcher : matcher}
			let i = ret.prefix.length;
			while( i<str.length ){
				let char = str.charAt(i);
				if( char == '\\' ){
					char = str.charAt(++i);
				}
				else if( char == schar ){
					matcher = str.substr(i).match(re);
					if( matcher ){
						ret.suffix = matcher[0];
						ret.content = str.substring(ret.prefix.length, i);
						ret.str = str.substring(0, i + ret.suffix.length);
						return ret;
					}
				}
				i++;
			}
		}
	},
}

class Segment {
	static generate = {
		data : {},
		push(char, fnMatch){
			if( !this.data[char] ){
				this.data[char] = [];
			}
			this.data[char].push(fnMatch);
		},
		contains(char){
			return this.data[char];
		},
		match(char, str, fstr, sidx){
			let funcs = this.data[char] || [];
			/* just for test.
			for(let i=0; i<funcs.length; i++){
				let idx = Math.floor(Math.random() * funcs.length);
				if( idx != i ){
					[funcs[i], funcs[idx]] = [funcs[idx], funcs[i]];
				}
			}
			*/
			for(let i=0; i<funcs.length; i++){
				let ret = funcs[i](str, {
					isPrelude(){
						let re = /\s/;
						let str = fstr;
						let i = sidx;
						while(i > 0){
							let char = str.charAt(--i);
							if( char == '\n' ){
								return true;
							}
							if( !re.test(char) ){
								return false;
							}
						}
						return true;
					}
				});
				if( ret ){
					return ret;
				}
			}
		}
	}
	static sbuild = function(segment){
		return {
			txt : '',
			data : [],
			nulline : 0,
			segment,
			str(c){
				this.txt += c;
			},
			push(segment){
				this.data.push(segment);
			},
			stred(){
				if( this.txt ){
					this.data.push(new SegmentText({
						str : this.txt,
						content : this.txt,
						parent : this.segment,
					}));
					this.txt = '';
				}
			},
			conclusion(callback, nullines = 2){
				this.stred();
				let segment = null;
				if( this.data.length ){
					if( this.isPureText() ){
						let csegment = this.data[0];
						this.segment.str = csegment.str;
						this.segment.content = csegment.content;
					}
					else {
						this.data.forEach(s => {
							this.segment.data.push(s);
							this.segment.str += s.str;
							this.segment.content += s.str;
						});
					}
					segment = this.segment;
				}
				else {
					this.nulline++;
					if( this.nulline >= nullines ){
						segment = this.segment;
					}
				}
				if( segment ){
					this.segment = callback(segment);
					this.nulline = 0;
					this.data = [];
				}
			},
			isPureText(){
				return this.data.length == 1 && (this.data[0] instanceof SegmentText || this.data[0] instanceof SegmentWrap);
			}
		};
	}
	constructor({str, content, parent = null , opts={}, attrs={}}){
		this.str = str;
		this.parent = parent;
		this.content = content;
		this.opts = Object.assign({}, opts);
		this.attrs = Object.assign({'class':[],style:{}}, attrs);
		this.data = [];
		if( this.isRoot() ){
			this.$ache = {};
			this.opts = Object.assign({
				defalutUnits : 'em', 
				allowStyle : {scope: null},
				htmlBlackList: ['script', 'iframe', 'form', 'object', 'style', 'embed'], 
				htmlWhiteList: []
			}, this.opts);
		}
		else {
			this.cache();
		}
		this.parse(...arguments);
	}
	cache(name){
		if( name ){
			let cache = this.root().$ache[name];
			if( cache ){
				return cache;
			}
		}
		else {
			name = this.constructor.name;
			let cache = this.root().$ache[name];
			if( !cache ){
				cache = this.root().$ache[name] = [];
			}
			cache.push(this);
		}
	}
	isRoot(){
		return !this.parent;
	}
	root(){
		let that = this;
		while(that.parent){
			that = that.parent;
		}
		return that;
	}
	parse(){
		let generate = () => new SegmentWrap({
			str : '', 
			content : '', 
			parent : this,
		});
		let sbuilder = new Segment.sbuild(generate());
		let i = 0, str = this.content;
		while( i < str.length ){
			let char = str.charAt(i);
			if( char == '\\' ){
				sbuilder.str(char);
				if( i+1 < str.length ){
					sbuilder.str(str.charAt(++i));
				}
				i++;
				continue;
			}
			if( Segment.generate.contains(char) ) {
				let generate = Segment.generate.match(char, str.substr(i), str, i);
				if( generate ){
					let segment = generate({parent: sbuilder.segment});
					sbuilder.stred();
					sbuilder.push(segment);
					i += segment.str.length;
					continue;
				}
			}
			if( char == '\n' ){
				sbuilder.conclusion(segment => {
					this.data.push(segment);
					return generate();
				});
			}
			else {
				sbuilder.str(char);
			}
			i++;
		}
		sbuilder.conclusion(segment => this.data.push(segment), Number.MAX_SAFE_INTEGER);
	}
	getAttributes(resolve){
		if( !resolve ){
			resolve = (attrs) => {
				return Object.keys(attrs).map(key => {
					let val = attrs[key];
					if( key == 'class' ){
						if( Array.isArray(val) ){
							val = val.join(' ');
						}
					}
					if( key == 'style' ){
						if( Array.isArray(val) ){
							val = val.join(';');
						}
						else if( typeof(val) === 'object' ){
							val = Object.keys(val).map(k => k+':'+val[k]).join(';');
						}
					}
					return val ? `${key}="${val}"` : '';
				}).filter(val => !!val).join(' ');
			}
		}
		return resolve(this.attrs);
	}
	print(resolve){
		return this.printGeneral('', '', (content) => {
			resolve([content.join('')]);
		});
	}
	printGeneral(s, e, resolve){
		let content = [s];
		if( this.data.length ){
			this.data.forEach(segment => {
				segment.print((strs) => {
					content = content.concat(strs);
				});
			});
		}
		else {
			content.push(this.getContent());
		}
		content.push(e);
		resolve(content);
	}
	getContent(){
		let chars = [];
		let str = this.content?.toString() || '';
		let i = -1;
		while( ++i<str.length ){
			let char = str.charAt(i);
			if( char == '\\' ){
				char = str.charAt(++i);
			}
			if( char == '<' ){
				char = this.getContentHTML(str.substr(i));
			}
			chars.push(char);
		}
		return chars.join('');
	}
	getContentHTML(str){
		let matcher = str.match(/^<\/?(\w+)(\s+|(\/?>))/);
		if( matcher ){
			let tag = matcher[1];
			let isAdmit = (() => {
				let htmlWhiteList = this.root().opts.htmlWhiteList;
				if( htmlWhiteList ){
					if( typeof(htmlWhiteList) == 'function' ){
						return htmlWhiteList(tag);
					}
					else if( Array.isArray(htmlWhiteList) ) {
						if( htmlWhiteList.length ){
							return !!htmlWhiteList.find(w => w.toUpperCase() == tag.toUpperCase());
						}
					}
					else {
						console.warn('wrong htmlWhiteList.', htmlWhiteList);
					}
				}
				let htmlBlackList = this.root().opts.htmlBlackList;
				if( typeof(htmlBlackList) == 'function' ){
					return !htmlBlackList(tag);
				}
				else if( Array.isArray(htmlBlackList) ) {
					if( htmlBlackList.length ){
						return !htmlBlackList.find(w => w.toUpperCase() == tag.toUpperCase());
					}
				}
				else {
					console.warn('wrong htmlBlackList.', htmlBlackList);
				}
				return false;
			})();
			
			return isAdmit ? '<' : '&lt;';
		}
		return '<';
	}
	subHtmlTag(str){
		return (str || '').replace(/</g, '&lt;');
	}
}

class SegmentText extends Segment {
	constructor(...args){ super(...args); }
	parse(){}
	print(resolve){
		let attrs = this.getAttributes();
		if( attrs ){
			return resolve('<span ' + this.getAttributes() + '>' + this.getContent() + '</span>');
		}
		return resolve(this.getContent());
	}
}

class SegmentWrap extends Segment {
	constructor(...args){ super(...args); }
	parse(){
		this.attrs['class'].push('wrap');
	}
	print(resolve){
		let getFirstext = () => {
			if( this.data.length ){
				if( this.data[0] instanceof SegmentText ){
					return this.data[0];
				}
			}
			else {
				return this;
			}
		};
		let segment = getFirstext();
		if( segment ){
			let matcher = segment.content.match(/^[ \t]+/);
			if( matcher ){
				let indent = matcher[0].split('').reduce((r, v) => r + (v == ' ' ? 1 : (v == '\t' ? 4 : 0)), 0);
				if( indent ){
					this.attrs.style['text-indent'] = indent + 'em';
				}
			}
		}
		/*
		let hasOtherAttrs = (attrs => {
			let isEmpty = (val) => {
				if( val == undefined || val == null || val == '' ){
					return true;
				}
				if( Array.isArray(val) ){
					return !val.length;
				}
				if( typeof(val) == 'object' ){
					return !Object.keys(val).find(k => !isEmpty(val[k]));
				}
				return false;
			}
			return attrs['class'].length != 1 
				|| attrs['class'][0] != 'wrap'
				|| Object.keys(attrs).find(key => key != 'class' && !isEmpty(attrs[key]));
		})(this.attrs);
		if( this.data.length == 1 && !hasOtherAttrs ){
			return this.printGeneral('', '', (content) => {
				resolve([content.join('')]);
			});
		}
		*/
		return this.printGeneral('<div ' + this.getAttributes() + '>', '</div>', (content) => {
			resolve([content.join('')]);
		});
	}
}

class SegmentMarkup extends Segment {
	static sbuild = function(segment){
		return {
			txt : '',
			data : [],
			str(c){
				this.txt += c;
			},
			stred(){
				if( this.txt ){
					this.data.push(new SegmentText({
						str : this.txt,
						content : this.txt,
						parent : segment,
					}));
					this.txt = '';
				}
			},
			push(segment){
				this.data.push(segment);
			},
			conclusion(segment){
				this.stred();
				if( this.data.length ){
					if( !this.isPureText() ){
						this.data.forEach(s => segment.data.push(s));
						this.data = null;
					}
				}
			},
			isPureText(){
				return this.data.length == 1 && this.data[0] instanceof SegmentText;
			}
		};
	}
	constructor(...args){ super(...args); }
	parseBefore(){}
	parseAfter(){}
	parsing(){
		let generate = () => new SegmentText({ str : '', content : '', parent : this });
		let sbuilder = new SegmentMarkup.sbuild(this);
		let i = 0, str = this.content;
		while(i < str.length ){
			let char = str.charAt(i);
			if( char == '\\' ){
				char = str.charAt(++i);
			}
			else if( Segment.generate.contains(char) ) {
				let generate = Segment.generate.match(char, str.substr(i), str, i);
				if( generate ){
					let segment = generate({parent: this});
					sbuilder.stred();
					sbuilder.push(segment);
					i += segment.str.length;
					continue;
				}
			}
			sbuilder.str(char);
			i++;
		}
		sbuilder.conclusion(this);
	}
	parse(...args){
		this.parseBefore(...args);
		this.parsing(...args);
		this.parseAfter(...args);
	}
}


class SegmentBox extends SegmentMarkup {
	static symbol = '[\0]';
	static wrap(str){
		return SegmentBox.symbol + str + SegmentBox.symbol;
	}
	constructor(){
		super(...arguments);
	}
	parseBefore(){
		this.attrs['class'].push('box');
	}
	print(resolve){
		return this.printGeneral('<div ' + this.getAttributes() + '>', '</div>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let paresult = parser.simple(str, /^\[\0\]/i, '[');
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentBox(Object.assign(args, opts));
	}
});

class SegmentHeading extends SegmentMarkup {
	constructor(){
		super(...arguments);
	}
	parseBefore({n}){
		this.n = n;
	}
	print(resolve){
		return this.printGeneral('<h' + this.n + ' ' + this.getAttributes() + '>', '</h' + this.n + '>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^(\[(\d+)\](.+))/i);
	if( matcher ){
		return (args) => new SegmentHeading(Object.assign(args, {str: matcher[1], n : matcher[2], content : matcher[3]}));
	}
	let paresult = parser.general(str, /^\[(\d+)\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentHeading(Object.assign(args, opts, {n : matcher[1]}));
	}
});

class SegmentBold extends SegmentMarkup {
	constructor(...args){
		super(...args);
	}
	print(resolve){
		return this.printGeneral('<b ' + this.getAttributes() + '>', '</b>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[b\](.+)/i);
	if( matcher ){
		return (args) => new SegmentBold(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[b\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentBold(Object.assign(args, opts));
	}
});


class SegmentItalic extends SegmentMarkup {
	constructor(...args){
		super(...args);
	}
	print(resolve){
		return this.printGeneral('<i ' + this.getAttributes() + '>', '</i>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[i\](.+)/i);
	if( matcher ){
		return (args) => new SegmentItalic(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[i\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentItalic(Object.assign(args, opts));
	}
});


class SegmentCenter extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('center'); }
	print(resolve){
		return this.printGeneral('<div ' + this.getAttributes() + '>', '</div>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[c\](.+)/i);
	if( matcher ){
		return (args) => new SegmentCenter(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[c\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentCenter(Object.assign(args, opts));
	}
});


class SegmentLeft extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('left'); }
	print(resolve){
		return this.printGeneral('<div ' + this.getAttributes() + '>', '</div>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[l\](.+)/i);
	if( matcher ){
		return (args) => new SegmentLeft(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[l\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentLeft(Object.assign(args, opts));
	}
});


class SegmentRight extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('right'); }
	print(resolve){
		return this.printGeneral('<div ' + this.getAttributes() + '>', '</div>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[r\](.+)/i);
	if( matcher ){
		return (args) => new SegmentRight(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[r\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentRight(Object.assign(args, opts));
	}
});


class SegmentUnderLine extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('under-line'); }
	print(resolve){
		return this.printGeneral('<span ' + this.getAttributes() + '>', '</span>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[[_u]\](.+)/i);
	if( matcher ){
		return (args) => new SegmentUnderLine(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[[_u]\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentUnderLine(Object.assign(args, opts));
	}
});


class SegmentDeleteLine extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('delete-line'); }
	print(resolve){
		return this.printGeneral('<span ' + this.getAttributes() + '>', '</span>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[[-ds]\](.+)/i);
	if( matcher ){
		return (args) => new SegmentDeleteLine(Object.assign(args, {str: matcher[0], content : matcher[1]}));
	}
	let paresult = parser.general(str, /^\[[-ds]\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentDeleteLine(Object.assign(args, opts));
	}
});


class SegmentFontsize extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({size}){ this.opts.size = size; }
	getAttributes(){
		let size = 1 + parseInt(this.opts.size) * 0.1;
		return [super.getAttributes()].concat(`style="font-size:${size}rem"`).join(' ');
	}
	print(resolve){
		return this.printGeneral('<span ' + this.getAttributes() + '>', '</span>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[([+-]\d+)\](.+)/i);
	if( matcher ){
		return (args) => new SegmentFontsize(Object.assign(args, {str: matcher[0], content : matcher[2], size: matcher[1]}));
	}
	let paresult = parser.general(str, /^\[([+-]\d+)\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		let size = matcher[1];
		return (args) => new SegmentFontsize(Object.assign(args, opts, {size}));
	}
});


class SegmentLink extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({link}){
		this.opts.link = link;
		if( /^([^\s\/\\]|[ ])+@.+(\.[\w_-]+)*\.[A-Za-z][\w_-]+(\?.+)?$/.test(link) ){
			this.attrs.href = 'mailto:' + link;
		}
		else {
			this.attrs.href = link.replace(/(javascript):/gi, '$1：');
		}
	}
	print(resolve){
		return this.printGeneral('<a ' + this.getAttributes() + '>', '</a>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[link\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		let {content} = opts;
		matcher = content.match(/\s([\S]+)$/m);
		if( matcher ){
			opts.link = matcher[1];
			opts.content = opts.content.substring(0, opts.content.length - opts.link.length).replace(/^\s+|\s+$/g, '');
		}
		else {
			opts.link = content.replace(/^\s+|\s+$/g, '');
		}
		return (args) => new SegmentLink(Object.assign(args, opts));
	}
});


class SegmentImage extends SegmentMarkup {
	constructor(...args){
		super(...args);
	}
	parsing({link, width, height}){

		Object.assign(this.opts, {link, width, height});

		let units = this.root().opts.defalutUnits;
		let style = this.attrs.style;
		if( width ){
			if( /^\d+(\.\d+)*$/.test(width) ){
				width += units;
			}
			style.width = width;
		}
		if( height ){
			if( /^\d+(\.\d+)*$/.test(height) ){
				height += units;
			}
			style.height = height;
		}
		this.attrs.src = link.replace(/(javascript):/gi, '$1：');
	}
	print(resolve){
		return this.printGeneral(`<figure><img ${this.getAttributes()} /><figcaption>`, '</figcaption></figure>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[(image|img|pic)\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		let {content} = opts;
		matcher = content.match(/\s(\S+)(\s+([\d\.%\w]+)*x([\d\.%\w]+)*)*$/m);
		if( matcher ){
			opts.link = matcher[1];
			opts.width = matcher[3];
			opts.height = matcher[4];
			opts.content = opts.content.substring(0, opts.content.length - matcher[0].length).replace(/^\s+|\s+$/g, '');
		}
		else {
			opts.link = content.replace(/^\s+|\s+$/g, '');
			opts.content = '';
		}
		return (args) => new SegmentImage(Object.assign(args, opts));
	}
});


class SegmentQuote extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({level}){
		this.level = level;
	}
	print(resolve){
		let level = ((segment) => {
			let level = 0;
			while( segment ) {
				if( segment instanceof SegmentQuote ){
					level = Math.max(segment.level, level);
				}
				segment = segment.parent;
			}
			return level;
		})(this.parent);

		if( level >= this.level ){
			return this.printGeneral('<div class="quote">', '</div>', resolve);
		}
		else {
			let s = new Array(this.level - level).fill('<blockquote class="blockquote">');
			let e = new Array(this.level - level).fill('</blockquote>');
			return this.printGeneral(s.join(''), e.join(''), resolve);
		}
	}
}
Segment.generate.push('>', (str, sup) => {
	if( !sup.isPrelude() ){
		return ;
	}
	let matcher = str.match(/^((>+)[ \t]+)/);
	if( matcher ){
		matcher = str.match(/^((>+)[ \t]+)(.+)*(\n((>+)[ \t]+)(.+)*)*/m);
		if( matcher ){
			let opts = {
				str : matcher[0],
				level : matcher[2].length,
				content : matcher[0].substr(matcher[1].length),
			};
			return (args) => new SegmentQuote(Object.assign(args, opts));
		}
	}
});
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[(>+)\s+/i);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentQuote(Object.assign(args, opts, {level : matcher[1].length}));
	}
});


class SegmentCodeBlock extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parsing({lang}){
		this.opts.lang = lang;
		this.content = this.content.replace(/^\n|\n$/g, '');
	}
	print(resolve){
		let lang = (lang => {
			if( this.opts.lang ){
				if( ['none', 'nil', 'null', 'nothing'].indexOf((this.opts.lang || '').toLowerCase()) != -1 ){
					return `class="none nohighlight"`
				}
				else {
					return this.subHtmlTag(`class="language-${this.opts.lang}"`);
				}
			}
			return '';
		})(this.opts.lang);
		let length = this.content.split('\n').length;
		let ul = ['<ol class="code-line">'].concat(new Array(length).fill('<li></li>')).concat('</ol>').join('');
		return this.printGeneral(`<div class="code-block">${ul}<pre class="code-text"><code ${lang}>`, '</code></pre></div>', resolve);
	}
}
Segment.generate.push('`', (str) => {
	let paresult = parser.simple(str, /^```(.*)/i, '`');
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentCodeBlock(Object.assign(args, opts, {lang : matcher[1] || ''}));
	}
});
Segment.generate.push('[', (str) => {
	let paresult = parser.simple(str, /^\[`(\S.+)*\]/i, '[');
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentCodeBlock(Object.assign(args, opts, {lang : matcher[1] || ''}));
	}
});


class SegmentCode extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parsing({lang}){ this.opts.lang = lang; }
	print(resolve){
		return this.printGeneral('<code class="code">', '</code>', resolve);
	}
}
Segment.generate.push('`', (str) => {
	if( !str.match(/^```/) ){
		let paresult = parser.simple(str, /^`/i, '`');
		if( paresult ){
			let {matcher, ...opts} = paresult;
			return (args) => new SegmentCode(Object.assign(args, opts));
		}
	}
});
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[`\s+/);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentCode(Object.assign(args, opts));
	}
});



class SegmentListItem extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore(){ this.attrs['class'].push('li'); }
	print(resolve){
		return this.printGeneral('<li ' + this.getAttributes() + '>', '</li>', resolve);
	}
}
class SegmentList extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({items, head}){
		super.parseBefore(...arguments);
		this.items = items;
		this.head = head;
		if( head ){
			let matcher = head.match(/type=(['"])(.+?)\1/i);
			if( matcher ){
				this.opts.type = matcher[2];
			}
		}
	}
	parseAfter({items}){
		this.items.forEach(item => {
			this.data.push(new SegmentListItem(Object.assign(item, {parent:this})));
		});
	}
	setListAttrs(clazz, listStyles){
		let type = (() => {
			if( this.opts.type ){
				if( !/\d+/.test(this.opts.type) ){
					return this.opts.type;
				}
				return listStyles[parseInt(this.opts.type)];
			}
		})();
		if( type ){
			this.attrs.style['list-style'] = type;
		}
		this.attrs['class'].push(clazz);
	}
}
SegmentList.generate = (symbol, tag, clazz) => {
	Segment.generate.push(symbol, (str, sup) => {
		if( !sup.isPrelude() ){
			return;
		}
		let re = /* /^-[ \t]+(.+)/ */new RegExp('^\\' + symbol + '[ \\t]+(.+)')
		let idx = 0;
		let items = [];
		let matcher = str.match(re);
		if( matcher ){
			let item = {
				str : matcher[0],
				content : matcher[1],
			};
			items.push(item);
			let i = idx = item.str.length;
			while( i<str.length ){
				if( str.charAt(i) == '\n' ){
					i++;
				}
				let nstr = str.substr(i);
				let matcher = nstr.match(re);
				if( matcher ){
					item = {
						str : matcher[0],
						content : matcher[1],
					};
					items.push(item);
					i = idx = i + item.str.length;
					continue;
				}
				let sre = /^  .+(\n  .+)*/
				matcher = nstr.match(sre);
				if( matcher ){
					item.str += matcher[0];
					item.content += SegmentBox.wrap(matcher[0].replace(/^  /mg, ''));
					i = idx = i + matcher[0].length;
					continue;
				}
				break;
			}
		}
		if( items.length ){
			return (args) => new clazz(Object.assign(args, {
				str : str.substring(0, idx),
				content : '',
				items,
			}));
		}
	});
	Segment.generate.push('[', (str) => {
		let re = /* /^\[ul\][ \t]*\n/i */new RegExp('^\\[' + tag + '\\s*(\\s+[\\w-]+=([\'"]).+\\2)*\\][ \\t]*\\n', 'i');
		let matcher = str.match(re);
		if( matcher ){
			let head = matcher[1];
			let items = [];
			let item = null;
			let idx = 0;
			let i = idx = matcher[0].length;
			while(i<str.length) {
				let nstr = str.substr(i);
				if( /^[ \t]*\n/.test(nstr) ){
					break;
				}
				matcher = nstr.match(/^(  |\t).+(\n\1.+)*/);
				if( matcher ){
					if( item ){
						item.str += matcher[0];
						item.content += SegmentBox.wrap(matcher[0].replace(/^(  |\t)/gm, ''));
					}
					else {
						item = {
							str : matcher[0],
							content : matcher[0].replace(/^(  |\t)/gm, ''),
						};
						items.push(item);
					}
					i += matcher[0].length;
				}
				else {
					matcher = nstr.match(/^.+/);
					if( matcher ){
						item = {
							str : matcher[0],
							content : matcher[0],
						};
						items.push(item);
						i += matcher[0].length;
					}
				}
				idx = i;
				if( str.charAt(i) == '\n' ){
					i++;
				}
			}
			if( items.length ){
				return (args) => new clazz(Object.assign(args, {
					str : str.substring(0, idx),
					content : '',
					items,
					head,
				}));
			}
		}
	});
}
class SegmentUnorderList extends SegmentList {
	constructor(...args){ super(...args); }
	parseBefore(){
		super.parseBefore(...arguments);
		this.setListAttrs('ul', 'none,disc,circle,square,disclosure-closed,disclosure-open'.split(','));
	}
	print(resolve){
		return this.printGeneral('<ul ' + this.getAttributes() + '>', '</ul>', resolve);
	}
}
SegmentList.generate('-', 'ul', SegmentUnorderList);

class SegmentOrderList extends SegmentList {
	constructor(...args){ super(...args); }
	parseBefore(){
		super.parseBefore(...arguments);
		this.setListAttrs('ol', [
			"decimal", "cjk-decimal", "decimal-leading-zero", "lower-roman", "upper-roman", "lower-greek", "lower-alpha", "lower-latin", "upper-alpha", "upper-latin", 
			"simp-chinese-informal", "cjk-ideographic", "simp-chinese-formal", "trad-chinese-informal", "trad-chinese-formal", "cjk-earthly-branch", "cjk-heavenly-stem",
			"armenian", "georgian", "hebrew", "ethiopic-numeric", "hiragana", "katakana", "hiragana-iroha", "katakana-iroha",
			"japanese-informal", "japanese-formal", "korean-hangul-formal", "korean-hanja-informal", "korean-hanja-formal",
			"arabic-indic", "bengali", "devanagari", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "gujarati", "gurmukhi", "hangul",
			"hangul-consonant", "kannada", "khmer", "lao", "malayalam", "myanmar", "oriya", "persian", "tamil", "telugu", "thai", "urdu",
		]);
	}
	print(resolve){
		return this.printGeneral('<ol ' + this.getAttributes() + '>', '</ol>', resolve);
	}
}
SegmentList.generate('+', 'ol', SegmentOrderList);


class SegmentTableCell extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({tag, align}){
		this.tag = tag || 'td';
		this.align = align;
		this.colspan = 0;
		this.rowspan = 0;
	}
	getAttributes(){
		if( this.align ){
			if( this.attrs['class'].indexOf(this.align) == -1 ){
				this.attrs['class'].push(this.align);
			}
		}
		this.attrs.colspan = this.colspan ? (this.colspan + 1) : undefined;
		this.attrs.rowspan = this.rowspan ? (this.rowspan + 1) : undefined;

		return super.getAttributes(...arguments);
	}
	print(resolve){
		return this.printGeneral(`<${this.tag} ${this.getAttributes()}>`, '</' + this.tag + '>', resolve);
	}
}
class SegmentTable extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parsing({head}){
		this.head = head;
		
		Object.assign(this.opts, {
			split : '\\s{2,}|\t+',
			CRLF : '',//' \\| ',
			thead : true,
		});
		let matcher = head.match(/split=(['"])(.+?)\1/i);
		if( matcher ){
			this.opts.split = matcher[2];
		}
		matcher = head.match(/thead=(['"])(0|1|true|false)\1/i);
		if( matcher ){
			this.opts.thead = ['1','true'].indexOf(matcher[2].toLowerCase()) != -1;
		}
		matcher = head.match(/CRLF=(['"])(.+?)\1/i);
		if( matcher ){
			this.opts.CRLF = matcher[2];
		}

		let align = {
			cache : [],
			parse(cell){
				let s = cell.charAt(0);
				let e = cell.charAt(cell.length - 1);
				if( s == '|' && e == '|' ){
					return 'center';
				}
				if( s == '|' ){
					return'left';
				}
				if( e == '|' ){
					return 'right';
				}
			},
			get(cell, colnum){
				let val = this.parse(cell);
				if( val ){
					this.cache[colnum] = val;
				}
				else {
					val = this.cache[colnum];
				}
				return val;
			}
		};
		this.rows = [];
		this.content.replace(/^\n|\n$/, '').split('\n').forEach((line, rownum) => {
			let row = [];
			line.replace(/^\s+/, '').split(new RegExp(this.opts.split)).forEach((cell, colnum) => {
				let content = cell.replace(/^\||\|$/g, '');
				if( content.replace(/\s/g, '') == '~' ){
					let idx = colnum;
					while( idx >= 0 ){
						let ncell = row[--idx];
						if( ncell ){
							ncell.colspan += 1;
							break;
						}
					}
				}
				else if( content.replace(/\s/g, '') == '^' ){
					let idx = rownum;
					while( idx >= 0 ){
						let nrow = this.rows[--idx] || [];
						let ncell = nrow[colnum];
						if( ncell ){
							ncell.rowspan += 1;
							break;
						}
					}
				}
				else {
					if( this.opts.CRLF ){
						content = content.replace(new RegExp(this.opts.CRLF, 'g'), '<br />');
					}
					row[colnum] = new SegmentTableCell({
						str : cell,
						content,
						parent : this,
						tag : (rownum == 0 && this.opts.thead) ? 'th' : 'td',
						align : align.get(cell, colnum)
					});
				}
			});
			this.rows.push(row);
		});
		this.maxcols = this.rows.reduce((r, row) => Math.max(r, row.length), 0);

		this.attrs['class'].push('table');
	}
	print(resolve){
		let html = ['<table ' + this.getAttributes() + '>'];
		let rows = [...this.rows];
		if( this.opts.thead ){
			html.push('<thead><tr>');
			(rows.shift() || []).forEach(cell => {
				cell.print(contents => {
					contents.forEach(c => html.push(c));
				});
			});
			html.push('</tr></thead>');
		}
		html.push('<tbody>');
		rows.forEach(row => {
			html.push('<tr>');
			row.forEach(cell => {
				cell.print(contents => {
					contents.forEach(c => html.push(c));
				});
			});
			html.push('</tr>');
		});
		html.push('</tbody>');
		html.push('</table>');
		resolve(html);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^(\[table\s*(\s+[\w-]+=(['"]).+\3)*(\])?)\s+/i);
	if( matcher ){
		let opts = {
			str : matcher[0],
			head : matcher[1],
		}
		if( matcher[4] ){
			matcher = str.substr(opts.str.length).match(/^(([\s\S]+?)\n)(\n|$)/m);
			if( matcher ){
				opts.str += matcher[1];
				opts.content = matcher[2];
				return (args) => new SegmentTable(Object.assign(args, opts));
			}
		}
		else {
			let paresult = parser.general(str, /^(\[table\s*(\s+[\w-]+=(['"]).+\3)*)\s+/i);
			if( paresult ){
				let {matcher, ...opts} = paresult;
				return (args) => new SegmentTable(Object.assign(args, opts, {head : matcher[1]}));
			}
		}
	}
});


class SegmentStyle extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parsing({head}){
		let matcher = head.match(/scope=(['"])(.+?)\1/i);
		if( matcher ){
			this.opts.scope = matcher[2];
		}
	}
	print(resolve){
		let opts = this.root().opts.allowStyle;
		if( !!opts ){
			let s = '', e = '';
			if( this.opts.scope ){
				s = this.subHtmlTag(this.opts.scope) + '{';
				e = '}';
			}
			if( opts.scope ){
				s = opts.scope + '{';
				e = '}';
			}
			return this.printGeneral('<style>' + s, e + '</style>', resolve);
		}
		return resolve('');
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^(\[style\s*(\s+[\w-]+=(['"]).+\3)*\])\n/i);
	if( matcher ){
		let opts = {
			str : matcher[0],
			head : matcher[1],
		};
		matcher = str.substr(opts.str.length).match(/^(([\s\S]+?)\n)(\n|$)/m);
		if( matcher ){
			opts.str += matcher[1];
			opts.content = matcher[2];
		}
		return (args) => new SegmentStyle(Object.assign(args, opts));
	}
});


class SegmentClass extends SegmentMarkup {
	constructor(){
		super(...arguments);
	}
	parseBefore({clazz}){
		this.opts.clazz = clazz;
		if( this.parent && !this.parent.isRoot() ){
			(clazz || '').split('.').forEach(c => this.parent.attrs['class'].push(c));
		}
		else {
			(clazz || '').split('.').forEach(c => this.attrs['class'].push(c));
		}
	}
	print(resolve){
		if( this.attrs['class'].length ){
			return this.printGeneral('<span ' + this.getAttributes() + '">', '</span>', resolve);
		}
		return this.printGeneral('', '', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[\.([^\[]+?)\](.+)/);
	if( matcher ){
		return (args) => new SegmentClass(Object.assign(args, {str: matcher[0], clazz : matcher[1], content : matcher[2]}));
	}
	let paresult = parser.general(str, /^\[\.(\S+)\s+/);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentClass(Object.assign(args, opts, {clazz : matcher[1]}));
	}
});


class SegmentID extends SegmentMarkup {
	constructor(){
		super(...arguments);
	}
	parseBefore({id}){
		this.opts.id = id;
		if( this.parent && !this.parent.isRoot() && !this.parent.attrs.id ){
			this.parent.attrs.id = id;
		}
		else {
			this.attrs.id = id;
		}
	}
	print(resolve){
		if( !this.attrs.id ){
			return this.printGeneral('', '', resolve);
		}
		return this.printGeneral('<span ' + this.getAttributes() + '>', '</span>', resolve);
	}
}
Segment.generate.push('[', (str) => {
	let matcher = str.match(/^\[#([\w_][\w\d-_]*)\](.+)*/);
	if( matcher ){
		return (args) => new SegmentID(Object.assign(args, {str: matcher[0], id : matcher[1], content : matcher[2] || ''}));
	}
	let paresult = parser.general(str, /^\[#([\w_][\w\d-_]*)\s+/);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		return (args) => new SegmentID(Object.assign(args, opts, {id : matcher[1]}));
	}
});

class SegmentSeparator extends SegmentMarkup {
	constructor(){ super(...arguments); }
	parseBefore({width, align}){
		Object.assign(this.opts, {width, align});
		if( width ){
			let units = this.root().opts.defalutUnits;
			if( /^\d+(\.\d+)*$/.test(width) ){
				width += units;
			}
			this.attrs.style.width = width;
		}
		if( align ){
			if( align == 'left' ){
				this.attrs.style['margin-left'] = 0;
			}
			else if( align == 'right' ){
				this.attrs.style['margin-right'] = 0;
			}
			else if( align == 'align' ){
				//this.attrs.style['margin'] = 'auto';
			}
		}
		this.attrs['class'].push('separator');
	}
	print(resolve){
		return resolve(['<hr ' + this.getAttributes() + '/>']);
	}
}
Segment.generate.push('-', (str, sup) => {
	if( sup.isPrelude() ){
		let matcher = str.match(/^(---+(\|*([\d\.%\w]*)\|*)*\s*)(\n|$)/);
		if( matcher ){
			let align = (opt => {
				if( opt ){
					let s = opt[0];
					let e = opt[opt.length - 1];
					if( s == '|' && e == '|' ){
						return 'center';
					}
					else if( s == '|' ){
						return 'left';
					}
					else if( e == '|' ){
						return 'right';
					}
				}
			})(matcher[2]);
			return (args) => new SegmentSeparator(Object.assign(args, {str: matcher[1], content: '', width: matcher[3], align}));
		}
	}
});


class SegmentFootnoteItem extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({label, num}){
		Object.assign(this.opts, {label, num});
	}
	print(resolve){
		let clazz = 'indent';
		if( this.opts.num || this.opts.label.length <= 3 ){
			clazz = 'rivet';
		}
		let label = this.opts.num || this.subHtmlTag(this.opts.label);
		return this.printGeneral(`<div class="${clazz}" id="footnote-${label}"><span class="label">[${label}]</span><span class="text">`, '</span></div>', resolve);
	}
}


class SegmentFootnote extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({type}){
		this.opts.type = type;
		if( type == 'auto' ){
			this.content = this.cache(SegmentFootnote.name).filter(segment => segment.opts.type == 'auto').length;
		}
		if( type == 'block' ){
			let content = this.content;
			let matcher = content.match(/^\s+/);
			if( matcher ){
				content = content.replace(new RegExp('^' + matcher[0], 'gm'), '');
			}
			let n = 0;
			let re = /^((.+?)[:：]([\s\S]+?)\n)((.+?)[:：]([\s\S]+?)\n)*/;
			let items = [];
			matcher = content.match(re);
			while(matcher){
				let opts = {
					str : matcher[1],
					label : matcher[2],
					content : matcher[3]
				};
				if( opts.label == '*' ){
					opts.num = ++n;
				}
				items.push(opts);
				content = content.substr(opts.str.length);
				matcher = content.match(re);
			}
			items.forEach(item => {
				this.data.push(new SegmentFootnoteItem(Object.assign(item, {parent: this})));
			});
		}
	}
	parsing(){
		if( this.opts.type == 'label' || this.opts.type == 'inline' || !this.data.length ){
			super.parsing(...arguments);
		}
	}
	print(resolve){
		if( this.opts.type == 'label' || this.opts.type == 'auto' ){
			let cache = this.cache(SegmentFootnoteItem.name);
			let segment = cache.find(segment => {
				if( this.opts.type == 'auto' ){
					return segment.opts.num == this.content;
				}
				if( this.opts.type == 'label' ){
					return segment.opts.label == this.content;
				}
			});
			let title = segment ? `title="${segment.getContent()}"` : '';
			let anchor = this.subHtmlTag('#footnote-' + this.content);
			return this.printGeneral(`<sup class="footnote-label">[<a href="${anchor}" ${title}>`, '</a>]</sup>', resolve);
		}
		if( this.opts.type == 'inline' ){
			return this.printGeneral('<sup class="footnote-inline">[', ']</sup>', resolve);
		}
		if( this.opts.type == 'block' ){
			return this.printGeneral('<section class="footnote-block">', '</section>', resolve);
		}
	}
}
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[\^([ \t])*(\n)?/);
	if( paresult ){
		let {matcher, ...opts} = paresult;

		if( matcher[2] ) {
			opts.type = 'block';
		}
		else if( matcher[1] ){
			opts.type = 'inline';
		}
		else if( opts.content == '*' ){
			opts.type = 'auto';
		}
		else {
			opts.type = 'label';
		}
		return (args) => new SegmentFootnote(Object.assign(args, opts));
	}
});



class SegmentReferenceItem extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({label}){
		Object.assign(this.opts, {label});
	}
}

class SegmentReference extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parseBefore({type}){
		this.opts.type = type;
		if( type == 'label' ){
			this.opts.label = this.content;
		}
		if( type == 'defines' ){
			let content = this.content;
			let matcher = content.match(/^\s+/);
			if( matcher ){
				content = content.replace(new RegExp('^' + matcher[0], 'gm'), '');
			}
			let re = /^((.+?)[:：]([\s\S]+?)\n)((.+?)[:：]([\s\S]+?)\n)*/;
			let items = [];
			matcher = content.match(re);
			while(matcher){
				let opts = {
					str : matcher[1],
					label : matcher[2],
					content : matcher[3]
				};
				items.push(opts);
				content = content.substr(opts.str.length);
				matcher = content.match(re);
			}
			items.forEach(item => {
				this.data.push(new SegmentReferenceItem(Object.assign(item, {parent: this})));
			});
		}
		if( type == 'define' ){
			let matcher = this.str.match(/^\[&((\S+)[ \t]*\n)/);
			if( matcher ){
				this.opts.label = matcher[2];
				this.content = this.content.replace(matcher[1], '');
				let content = this.content;
				matcher = content.match(/^\s+/);
				if( matcher ){
					content = content.replace(new RegExp('^' + matcher[0], 'gm'), '');
				}
				let opts = {
					str : this.content,
					label : this.opts.label,
					content,
				}
				this.data.push(new SegmentReferenceItem(Object.assign(opts, {parent: this})));
			}
		}
	}
	parsing(){
		if( this.opts.type == 'label' || !this.data.length ){
			super.parsing(...arguments);
		}
	}
	print(resolve){
		if( this.opts.type == 'label' ){
			let cache = this.cache(SegmentReferenceItem.name);
			let segment = cache.find(segment => segment.opts.label == this.opts.label);
			if( segment ){
				return segment.print(...arguments);
			}
			else {
				super.print(...arguments);
			}
		}
		else {
			return this.printGeneral('<div class="reference-define hide">', '</div>', resolve);
		}
	}
}
Segment.generate.push('[', (str) => {
	let paresult = parser.general(str, /^\[&([ \t])*(\n)?/);
	if( paresult ){
		let {matcher, ...opts} = paresult;
		if( matcher[2] ) {
			opts.type = 'defines';
		}
		else if( matcher[1] ){
			opts.type = 'label';
		}
		else {
			opts.type = 'define';
		}
		return (args) => new SegmentReference(Object.assign(args, opts));
	}
});

/*
class SegmentHTML extends SegmentMarkup {
	constructor(...args){ super(...args); }
	parsing({tag}){
		this.opts.tag = tag;

		let isAdmit = true;
		let htmlWhiteList = this.root().opts.htmlWhiteList;
		if( htmlWhiteList ){
			if( typeof(htmlWhiteList) == 'function' ){
				isAdmit = htmlWhiteList(tag);
			}
			else if( Array.isArray(htmlWhiteList) ) {
				isAdmit = htmlWhiteList.find(w => w.toUpperCase() == tag.toUpperCase());
			}
			else {
				console.warn('wrong htmlWhiteList.' + htmlWhiteList);
			}
		}
		else {
			let htmlBlackList = this.root().opts.htmlBlackList;
			if( typeof(htmlBlackList) == 'function' ){
				isAdmit = !htmlBlackList(tag);
			}
			else if( Array.isArray(htmlBlackList) ) {
				isAdmit = !htmlBlackList.find(w => w.toUpperCase() == tag.toUpperCase());
			}
			else {
				console.warn('wrong htmlBlackList.' + htmlBlackList);
			}
		}
		this.content = isAdmit ? '<' : '&lt;'
	}
}
Segment.generate.push('<', (str) => {
	let matcher = str.match(/^<\/?(\w+)(\s+|(\/?>))/);
	if( matcher ){
		return (args) => new SegmentHTML(Object.assign(args, {str:'<', content:'', tag:matcher[1]}));
	}
});
*/


class Markup {
	constructor({content, opts={}}){
		this.segment = new Segment({content: this.CRLF(content), opts});
		this.event = new Event(this);
		this.opts = opts;
	}
	CRLF(str){
		if( !this.crlf ){
			if( str.indexOf('\r\n') != -1 ){
				this.crlf = '\r\n';
			}
			else if( str.indexOf('\r') != -1 ){
				this.crlf = '\r';
			}
			else {
				this.crlf = '\n';
			}
			return this.crlf == '\n' ? str : str.replace(new RegExp(this.crlf, 'g'), '\n');
		}
		else {
			return this.crlf == '\n' ? str : str.replace(/\n/g, this.crlf);
		}
	}
	print(resolve){
		if( typeof(this.opts.highlight) == 'function' ){
			let cache = this.segment.cache(SegmentCodeBlock.name) || [];
			if( cache.length ){
				this.event.add('printed', this.opts.highlight)
			}
		}
		if( typeof(resolve) == 'string' ){
			if( typeof(document) === 'object' && typeof(document.querySelector) === 'function' ){
				resolve = document.querySelector(resolve);
			}
		}
		if( this.isDom(resolve) ){
			let $dom = resolve;
			resolve = (content) => {
				$dom.innerHTML = content;
			}
		}
		if( typeof(resolve) != 'function' ){
			throw new Error('[print]unexpected arguments.' + resolve);
		}
		this.segment.print(content => {
			if( typeof(resolve) == 'function' ){
				resolve(this.CRLF(content.join('\n')));
			}
			this.event.dispatch('printed');
		});
		return this;
	}
	isDom(object){
		if( object ){
			if( typeof(HTMLElement) === 'object' ){
				return object instanceof HTMLElement;
			}
			if( typeof(Element) === 'object' ){
				return object instanceof Element;
			}
			return typeof(object) === 'object' && typeof(object.nodeType) === 'number' && typeof(object.nodeName) === 'string';
		}
		return false;
	}
}

/*
(function(){
	if (typeof module === 'object' && typeof module.exports === 'object') {
		module.exports = {Markup};
	}
	else {
		if (typeof window === 'object') {
			window.Markup = Markup;
		}
	}
})();
*/

export {
	/*
	Event
	parser
	Segment
	SegmentText
	SegmentWrap
	SegmentMarkup
	SegmentBox
	SegmentHeading
	SegmentBold
	SegmentItalic
	SegmentCenter
	SegmentLeft
	SegmentRight
	SegmentUnderLine
	SegmentDeleteLine
	SegmentFontsize
	SegmentLink
	SegmentImage
	SegmentQuote
	SegmentCodeBlock
	SegmentCode
	SegmentListItem
	SegmentList
	SegmentUnorderList
	SegmentOrderList
	SegmentTableCell
	SegmentTable
	SegmentSeparator
	SegmentFootnoteItem
	SegmentFootnote
	SegmentReferenceItem
	SegmentReference
	SegmentStyle
	SegmentClass
	SegmentID,
	*/
	Markup,
};
